package imessage

import (
	"encoding/json"
	"fmt"
	_ "github.com/Pinidea/oss-aliyun-go"
	"github.com/boltdb/bolt"
	"io"
	"time"
)

var (
	TABLE_BLOCK           = []byte("im_block")
	TABLE_GROUP           = []byte("im_group")
	TABLE_OFFLINE_MESSAGE = []byte("im_offline_message")
)

type User struct {
	Name   string
	Blocks []string
}

type Group struct {
	GName      string
	Owner      string
	CreateTime time.Time
	MaxMember  int
	Members    []string
}

type IMStorage struct {
	file string
	db   *bolt.DB
}

func NewIMStorage(file string) (s *IMStorage, err error) {
	db, err := bolt.Open(file, 0600, &bolt.Options{Timeout: 30 * time.Second})
	if err != nil {
		return nil, err
	}

	err = db.Update(func(tx *bolt.Tx) error {
		if _, err := tx.CreateBucketIfNotExists(TABLE_GROUP); err != nil {
			return err
		}
		if _, err := tx.CreateBucketIfNotExists(TABLE_BLOCK); err != nil {
			return err
		}
		if _, err := tx.CreateBucketIfNotExists(TABLE_OFFLINE_MESSAGE); err != nil {
			return err
		}
		return nil
	})
	s = &IMStorage{db: db, file: file}
	return s, err
}

func (s *IMStorage) Block(who, blocker string) error {
	tx, err := s.db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	u := &User{Name: who, Blocks: []string{}}
	if val := tx.Bucket(TABLE_BLOCK).Get([]byte(who)); val != nil && len(val) > 0 {
		if err := json.Unmarshal(val, u); err != nil {
			return err
		}
	}
	u.Blocks = append(u.Blocks, blocker)
	b, err := json.Marshal(u)
	if err != nil {
		return err
	}

	if err = tx.Bucket(TABLE_BLOCK).Put([]byte(who), b); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}
	return nil
}

func (s *IMStorage) FindBlocks(who string) ([]string, error) {
	tx, err := s.db.Begin(true)
	if err != nil {
		return []string{}, err
	}
	defer tx.Rollback()
	u := &User{Name: who, Blocks: []string{}}
	if val := tx.Bucket(TABLE_BLOCK).Get([]byte(who)); val != nil && len(val) > 0 {
		if err := json.Unmarshal(val, u); err != nil {
			return []string{}, err
		}
	}
	if err := tx.Commit(); err != nil {
		return []string{}, err
	}
	return u.Blocks, nil
}

func (s *IMStorage) CreateGroup(owner, gname string, max_member int) error {
	tx, err := s.db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	g := &Group{GName: gname, Owner: owner, CreateTime: time.Now(), MaxMember: max_member, Members: []string{}}

	if val := tx.Bucket(TABLE_GROUP).Get([]byte(gname)); val != nil && len(val) > 0 {
		return fmt.Errorf("group %v exist", gname)
	}
	b, err := json.Marshal(g)
	if err != nil {
		return err
	}
	if err = tx.Bucket(TABLE_GROUP).Put([]byte(gname), b); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}
	return nil
}

func (s *IMStorage) RemoveGroup(gname string) error {
	tx, err := s.db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	if err = tx.Bucket(TABLE_GROUP).Delete([]byte(gname)); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}
	return nil
}

func (s *IMStorage) JoinGroup(member, gname string) error {
	tx, err := s.db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	g := &Group{}
	if val := tx.Bucket(TABLE_GROUP).Get([]byte(gname)); val == nil || len(val) == 0 {
		return fmt.Errorf("group %v not exist", gname)
	} else if err := json.Unmarshal(val, g); err != nil {
		return err
	}
	if g.MaxMember == len(g.Members) {
		return fmt.Errorf("group %v is full", gname)
	}
	g.Members = append(g.Members, member)

	b, err := json.Marshal(g)
	if err != nil {
		return err
	}
	if err = tx.Bucket(TABLE_GROUP).Put([]byte(gname), b); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}
	return nil
}

func (s *IMStorage) LeaveGroup(member, gname string) error {
	tx, err := s.db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	g := &Group{}
	if val := tx.Bucket(TABLE_GROUP).Get([]byte(gname)); val == nil || len(val) == 0 {
		return fmt.Errorf("group %v not exist", gname)
	} else if err := json.Unmarshal(val, g); err != nil {
		return err
	}
	members := []string{}
	for _, m := range g.Members {
		if member != m {
			members = append(members, m)
		}
	}
	g.Members = members

	b, err := json.Marshal(g)
	if err != nil {
		return err
	}
	if err = tx.Bucket(TABLE_GROUP).Put([]byte(gname), b); err != nil {
		return err
	}

	if err := tx.Commit(); err != nil {
		return err
	}
	return nil
}

func (s *IMStorage) FindGroup(gname string) (*Group, error) {
	tx, err := s.db.Begin(true)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	g := &Group{}
	if val := tx.Bucket(TABLE_GROUP).Get([]byte(gname)); val == nil || len(val) == 0 {
		return nil, fmt.Errorf("group %v not exist", gname)
	} else if err := json.Unmarshal(val, g); err != nil {
		return nil, err
	}
	if err := tx.Commit(); err != nil {
		return nil, err
	}
	return g, nil
}

func (s *IMStorage) PutOfflineMessage(member string, msg *Message) error {
	tx, err := s.db.Begin(true)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	b := tx.Bucket(TABLE_OFFLINE_MESSAGE).Bucket([]byte(member))
	if b.Stats().KeyN >= 1024 {
		return fmt.Errorf("%v over size", b.Stats().KeyN)
	}
	if bb, err := json.Marshal(msg); err != nil {
		return err

	} else {
		if seq, err := b.NextSequence(); err != nil {
			return err
		} else if err := b.Put([]byte(fmt.Sprintf("%v", seq)), bb); err != nil {
			return err
		}

	}

	if err := tx.Commit(); err != nil {
		return err
	}
	return nil
}

func (s *IMStorage) FindOfflineMessage(member string) ([]*Message, error) {
	tx, err := s.db.Begin(true)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()
	b := tx.Bucket(TABLE_OFFLINE_MESSAGE).Bucket([]byte(member))
	ms := make([]*Message, 0)
	if err := b.ForEach(func(k, v []byte) error {
		m := &Message{}
		if err := json.Unmarshal(v, m); err != nil {
			syslog.Println("error", err)
		} else {
			ms = append(ms, m)
			if err = b.Delete(k); err != nil {
				syslog.Println("error", err)
			}
		}
		return nil
	}); err != nil {
		return nil, err
	}
	if err := tx.Commit(); err != nil {
		return nil, err
	}
	return ms, nil
}

/**
attach_url_prefix=http://oss-cn-qingdao.aliyuncs.com/imattach/
aliyun_oss_appid=zSy0T8jNnhxZPluB
aliyun_oss_appkey=zNomi7Fuo9GSqsOw3yWgg5aJ7Bn5xH
aliyun_oss_endpoint=oss-cn-qingdao.aliyuncs.com
aliyun_oss_bucket=imattach
**/
func (s *IMStorage) PutAttach(key string, content io.ReadCloser) error {
	return nil
	// appid := GetString("aliyun_storage", "aliyun_oss_appid", "appid")
	// appkey := GetString("aliyun_storage", "aliyun_oss_appkey", "key")
	// endpoint := GetString("aliyun_storage", "aliyun_oss_endpoint", "oss.aliyuncs.com")
	// prefix := GetString("aliyun_storage", "attach_url_prefix", "http://oss.aliyuncs.com/imattach/")
	// bucket := GetString("aliyun_storage", "aliyun_oss_bucket", "imattach")
	// o := oss.New(oss.DefaultRegion, appid, appkey)
	// return o.Bucket(bucket).Put(key, content, "", "")
}
